Skip to content

[SPIKE] Replace DynamicScope with a render scope tracker#21487

Closed
NullVoxPopuli-ai-agent wants to merge 2 commits into
emberjs:mainfrom
NullVoxPopuli-ai-agent:remove-dynamic-scope
Closed

[SPIKE] Replace DynamicScope with a render scope tracker#21487
NullVoxPopuli-ai-agent wants to merge 2 commits into
emberjs:mainfrom
NullVoxPopuli-ai-agent:remove-dynamic-scope

Conversation

@NullVoxPopuli-ai-agent

Copy link
Copy Markdown
Contributor

Summary

Spike: remove the VM's DynamicScope concept entirely, replacing it with a minimal
render scope tracker that carries Ember's two ambient values (outletState and the
classic-component view chain).

Stacked on #21485 (its commit is included here): that PR removes the template-facing
dynamic vars (-with-dynamic-vars / -get-dynamic-var); this one removes the machinery
underneath. Related to (but not dependent on) the closed #21450: the tracker here is the
internal core of what that PR's glimmer side did — the render-tree-positional
provide/lookup mechanism — without any public createContext API. If a Context feature
lands later, it can grow out of this same mechanism instead of a parallel one.

The replacement mechanism

RenderScopeStackImpl (@glimmer/runtime/lib/render-scope.ts): a parent-linked tree
with one node per component that has the (renamed) renderScope capability. A component
provides values on its own node during create; descendants read the nearest
provided value by walking the parent chain.

Two properties make this a faithful replacement for dynamic scope:

  1. Provide-on-own-node means isolation is free. Dynamic scope had to push a fresh
    child scope around every component that might contain a mutating descendant, so
    custom-manager (Glimmer) components all carried dynamicScope: true purely to stop
    set from leaking to siblings. With provides landing on the provider's own node,
    nothing can leak, and custom-manager components flip to renderScope: false — fewer
    scope frames than before, not more.
  2. Update re-entry is handled by paired updating opcodes. The old ops 59/60 became
    VM_PUSH_RENDER_SCOPE_OP / VM_POP_RENDER_SCOPE_OP; on the append pass they push/pop
    nodes and install EnterRenderScopeOpcode/ExitRenderScopeOpcode in the updating
    opcode list. The updating VM therefore re-establishes the correct node as it descends,
    so content re-rendered mid-update (a TryOpcode re-rendering after a route change,
    an {{#if}} flipping around an {{outlet}}) sees the same ambient state it would on
    initial render. This is the job closure capture of dynamicScope used to do.

Removed

  • DynamicScope interface (get/set/child), DynamicScopeImpl
  • The VM's dynamic scope stack, pushDynamicScope/popDynamicScope/dynamicScope(),
    and the dynamicScope field captured in every block closure (ClosureState/VMState)
  • The dynamicScope parameter threaded through renderMain/renderComponent,
    WithCreateInstance#create, WithUpdateHook#update, and the internal helper signature
  • Ember's DynamicScope class in renderer.ts (and its outletState-only assertions)
  • The fromDynamicScope test-infra feature in emberish-curly

Renamed / migrated

  • Capability dynamicScoperenderScope (same bit); ops 59/60 renamed as above
  • Ember's ambient values live in @ember/-internals/glimmer/lib/utils/render-scope.ts
    as private symbols: the outlet component manager provides OUTLET_STATE; the
    {{outlet}} helper reads the nearest one (internal helpers now receive the current
    RenderScopeNode instead of a DynamicScope); curly/root managers provide/read VIEW
    for the classic parentView chain

Testing

  • pnpm type-check:internals — clean (the two @glimmer/component errors in owner
    type-tests are pre-existing on main)
  • pnpm lint:eslint — clean
  • vite build + full testem browser suite — 9382 tests, 9365 pass, 17 skip, 0 fail
    (identical tally to main + [SPIKE] Remove -with-dynamic-vars / -get-dynamic-var support #21485, so outlet nesting, engines, and the classic
    parentView chain are all exercised and passing)
  • grep -ri dynamicscope packages/ — zero remaining references

🤖 Generated with Claude Code

NullVoxPopuli and others added 2 commits July 6, 2026 13:15
Removes the template-facing dynamic vars feature:

- the -get-dynamic-var (call/append) and -with-dynamic-vars (block)
  keywords, the GetDynamicVar/WithDynamicVars MIR nodes, their encoding
  and strict-mode passes
- wire format opcodes WithDynamicVars (45) and GetDynamicVar (53) and
  their types (numbers left as gaps, not renumbered)
- VM opcodes GetDynamicVar (111) and BindDynamicScope (58), their
  append-opcode handlers, VM#bindDynamicScope, and debug metadata
- the associated integration test suites

Dynamic scope itself stays: Ember's outlet machinery reads and writes
outletState on the dynamic scope directly from JS, and component
invocation still pushes/pops dynamic scope. Only the template syntax
for reading/writing arbitrary dynamic vars is removed.

Note: this deletes the intimate API liquid-fire uses to intercept
outletState in templates.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Removes the VM's DynamicScope concept (interface, impl, per-closure
capture, manager/helper parameters) and replaces it with a minimal
render scope tracker: a parent-linked node tree where components with
the renamed renderScope capability provide ambient values on their own
node and descendants read the nearest provided value.

Because provides land on the provider's own node, sibling isolation is
free, and custom-manager components no longer need a scope frame at all
(they only had dynamicScope: true to prevent set() leaking to
siblings). Update-path correctness — previously handled by capturing
dynamicScope in every block closure — is handled by paired
Enter/ExitRenderScopeOpcode updating opcodes that re-establish the
current node as the updating VM descends.

Ember's two ambient values migrate to private symbol keys in
utils/render-scope.ts: the outlet component manager provides
OUTLET_STATE and the {{outlet}} helper reads the nearest one; curly and
root managers provide/read VIEW for the classic parentView chain.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@NullVoxPopuli NullVoxPopuli mentioned this pull request Jul 6, 2026
11 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants